#ifndef __rastermap_h__

#define __rastermap_h__

#include <FastLED.h>

#ifdef Serial

#define PrintStr Serial.print
#define PrintDec(val) Serial.print(val, DEC)

#else

#include <Arduino.h>
#include <stdio.h>
#include <stdargs.h>
#define PrintStr printf
#define PrintDec(val) printf("%d", val);

#endif

const unsigned char MAX_COLOR = 255;

inline CRGB RandomPixel() {
    return random(0, 0xFFFFFF);
}

inline CHSV RandomHSV(int mins, int minv) {
    return CHSV(random(0, MAX_COLOR), random(mins, MAX_COLOR), random(minv, MAX_COLOR));
}

// Base class for all raster objects.  Raster objects have a width
// a height and the ability to read and write colors at x and y.
// Plus a bunch of built in functions.
class Raster {
  int width_;
  int height_;

 public:
  inline Raster(int width, int height)
  : width_(width),
    height_(height) {
  }

  // Changed by sub-classes.  Note that all sub-classes must
  // implement a linear access verison of itself.  Underneath
  // it all, all rasters are expected to be a one dimensional
  // array of sorts.
  virtual void SetPixel(int index, const CRGB& color) = 0;
  virtual const CRGB& GetPixel(int index) const = 0;

  inline int width() const { return width_; }
  inline int height() const { return height_; }

  // A convenience version of set-pixel.
  inline void SetPixel(int x, int y, int r, int g, int b) {
      SetPixel(x, y, CRGB(r, g, b));
  }

  // Detect whether or not a coordinate is in bounds of the raster.
  inline int IsIn(int x, int y) const {
    return (x >=0 && x < width() &&
            y >= 0 && y < height());
  }

  // Set a single pixel.  Any coordinate given which is not in
  // bounds counts as a nop.
  inline void SetPixel(int x, int y, CRGB color) {
    SetPixel((y * width_) + x, color);
  }

  // Get a pixel.  Any coordinate given which is not in bounds
  // returns HSV_BLACK.
  inline const CRGB& GetPixel(int x, int y) const {
    if (!IsIn(x, y)) return CRGB::Black;
    return GetPixel((y * width_) + x);
  }

  // Draw one raster in to this raster at a specific coordinate.
  // Coordinate can be out of bounds, any pixels from source
  // raster that are not in bound are ignored.
  //
  // Moving image data between rasters is preferable than trying
  // to mem copy because remapped rasters do not displa contiguous
  // bits of information.
  void Blt(const Raster& source, int x, int y) {
    int fromx = 0;
    int fromy = 0;
    int to_width = source.width();
    int to_height = source.height();

    // Do not copy stuff that is out of bounds
    if (x < 0)
      fromx -= x;  // Actually adds to from x

    if (y < 0)
      fromy -= y;  // Actually adds to y

    if (x + to_width >= width()) {
      to_width -= x;
    }

    if (y + to_height >= height()) {
      to_height -= y;
    }

    // Do copy
    for (int posy = fromy; posy <= to_height; ++posy) {
      for (int posx = fromx; posx <= to_width; ++posx) {
        SetPixel(x + posx, y + posy, source.GetPixel(posx, posy));
      }
    }
  }

  // Clear whole raster to black.
  inline void Clear() {
    Clear(CRGB::Black);
  }

  // Clear whole raster to specified color.
  void Clear(const CRGB& color) {
    for (int index = 0; index < width() * height(); ++index) {
      SetPixel(index, color);
    }
  }

  void Dim(unsigned char by) {
      for (int y = 0; y < height(); ++y) {
        for (int x = 0; x < width(); ++x) {
            CRGB color = GetPixel(x, y);
            color.fadeLightBy(by);
            SetPixel(x, y, color);
        }
      }
  }
};


// An actual bitmapped raster that writes to a contiguous underlying
// array of colors.
//
// It does not try to allocate any memory so that all rasters can
// be allocated in program memory rather than RAM, since there is
// more of it available on an arduino.
class BitMap : public Raster {
  CRGB* color_array_;
  int buffer_size_;

 public:
  virtual void SetPixel(int index, const CRGB& color) {
    if (index < 0 || index >= buffer_size_) return;
    color_array_[index] = color;
  }

  virtual const CRGB& GetPixel(int index) const {
    if (index < 0 || index >= buffer_size_) return CRGB::Black;
    return color_array_[index];
  }

  // Keeps a reference to color_array and will modify it's content
  // if SetPixel is called.
  inline BitMap(CRGB* color_array, int width, int height)
  : Raster(width, height),
    color_array_(color_array),
    buffer_size_(width * height) {
  }

  virtual inline void SetColorArray(CRGB* color_array) {
    color_array_ = color_array;
  }
};

template <int w, int h>
class Buffer : public BitMap {
  CRGB buffer_[h * w];
public:
  Buffer()
  : BitMap(buffer_, w, h) {
  }
};

// Special mapping bitmap for weirdly arranged LED matrices.
class RasterMapper : public Raster {
  const int* index_map_;
  Raster* raster_;
  int index_size_;

public:
  // index_map is an array of integers covering the full bitmap
  // area that maps (x, y) coordinates to individual indexes of
  // the underlying color_array.  Use -1 to indicate no mapping.
  // Calling GetPixel on a coordinate that is mapped to -1 returns
  // CRGB::Black.  Calling SetPixel on a coordinate that is mapped
  // to -1 is a nop.
  //
  // color_array may well be smaller than width * height.
  RasterMapper(const int* index_map,
               Raster* raster,
               int width, int height)
  : Raster(width, height),
    index_map_(index_map),
    index_size_(height * width),
    raster_(raster) {
    // Do check to make sure all indexes are valid.
    for (int x = 0; x < width; ++x) {
      for (int y = 0; y < height; ++y) {
        int index = (width * y) + x;
        int mapped_index = index_map[index];
        if (mapped_index < 0)
          continue;
        if (mapped_index > width * height) {
          PrintStr("Bad index ");
          PrintDec(mapped_index);
          PrintStr(" at (");
          PrintDec(x);
          PrintStr(", ");
          PrintDec(y);
          PrintStr(")\n");
        }
        if (mapped_index >= raster->width()) {
          PrintStr("Bad index ");
          PrintDec(mapped_index);
          PrintStr(" at (");
          PrintDec(x);
          PrintStr(", ");
          PrintDec(y);
          PrintStr(")\n");
        }
      }
    }
  }

  void SetPixel(int index, const CRGB& color) {
    if (index < 0 || index >= index_size_) return;
    int light_index = index_map_[index];
    raster_->SetPixel(light_index, 0, color);
  }

  const CRGB& GetPixel(int index) const {
    if (index < 0 || index >= index_size_) return CRGB::Black;
    int light_index = index_map_[index];
    return raster_->GetPixel(light_index, 0);
  }

  inline void SetIndexMap(int* index_map) {
    index_map_ = index_map;
  }
};

void RGBToHSV(const CRGB& rgb, CHSV& hsv)
{
  unsigned char min_val, max_val, delta, hue, sat, val;

  min_val = min(rgb.r, min(rgb.g, rgb.b));
  max_val = max(rgb.r, max(rgb.b, rgb.b));

  val = max_val;
  delta = max_val - min_val;
  if (max_val > 0) {
    sat = delta / max_val;
  } else {
    hsv.setHSV(0, 0, 0);
    return;
  }

  if (rgb.r >= max_val ) {
    hue = (rgb.g - rgb.b) / delta;
  }
  else {
    if (rgb.g >= max_val)
      hue = 1 + (rgb.b - rgb.r) / delta;
    else
      hue = 2 + (rgb.r - rgb.g) / delta;
  }

  if (hue < 0) {
    hue += 255;
  }

  hsv.setHSV(hue, sat, val);
}

#endif
